#ifndef MATRIX4_HPP
#include "Matrix4.hpp"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <list>
#include <deque>
//#include <GL/glut.h>
#include <GLUT/glut.h> //mac os
#include "Camera.hpp"
#include <time.h>
#include "HW7.h"
#include <vector>
#include "tree.hpp"
#include "objreader.h"


using namespace std;
  // This data structure defines a simple house


int Window::width = 600;
int Window::height = 900;
int Window::radius = min(Window::height, Window::width)/2;
static HW7 hw7;
bool moveLeft = false;
bool moveRight = false;

float frustumSide = 4, frustumNear = 4, frustumFar = 100.0;
float offset = 0;
Vector3 camV = Vector3(0, 10, 5);
Vector3 tarV = Vector3(0,0,0);
Vector3 upV = Vector3(0,1,0);
GLuint texture[2];     // storage for tw textures

Tree firstTree;

int nVerts;
float *vertices;
float *normals;
float *texcoords;
int nIndices;
int *indices;

Matrix4 modelMatrix;
Camera camera1 = Camera(camV, tarV, upV);
//Camera camera1 = Camera(Vector3(0,0,0), Vector3(0,0,-1), Vector3(0,1,0));
int mouseTargetX=1;
int mouseTargetY=1;
int mouseTargetZ=1;

struct tree {
	Vector3 pos;
	float radius;
	bool beenHit;
	Tree * treeObj;
	//pointer to actual tree
} ;

struct hw7Rect {
	float left;
	float right;
	float front;
	float back;
} ;

list<tree> treeList;
deque<Tree*> treeObjList;
hw7Rect ground;
float playerX = 0;
float playerRad = .3;
int numHits = 0;


static GLfloat spin = 0.0, spin2 = 0.0, walk = 0.0, spin3 = 0.0;
/*void spinDisplay(void)
{
	spin = spin + 1.25;
	if (spin > 360.0)
		spin = spin - 360.0;
	glutPostRedisplay();
}*/

int getRandIndex(){
	int rsize=(int) treeObjList.size();
	return rand() % rsize;
}
float getRandX() {
	return ((rand() % 1001)/100.0)-5.0;
}
float getRandZ() {
	return ((rand() % 201)/100.0)-1.0;
}
tree genTree() {
	
	tree tmp;
	tmp.beenHit = false;
	tmp.radius = .5;
	tmp.pos = Vector3(getRandX(), 1, -offset -20 + getRandZ());
	tmp.treeObj = treeObjList.at(getRandIndex());
	return tmp;
}
tree genTree(float x, float y, float z) {
	
	tree tmp;
	tmp.beenHit = false;
	tmp.radius = .5;
	tmp.pos = Vector3(x, y, z);
	tmp.treeObj = treeObjList.at(getRandIndex());
	
	return tmp;
}

void genTreeObjList() {
	srand ( time(NULL) );
	for (int i=0;i<100;i++)
		treeObjList.push_back(new Tree("A",3));
}
void init() {
	genTreeObjList();
	
	ground.left = -50;
	ground.right = 50;
	ground.front = -100;
	ground.back = 100;
	for (int i = -20; i>-49; i-=2) {
		treeList.push_back(genTree(-3, 1, i));
		treeList.push_back(genTree(3, 1, i));
	}
	ObjReader::readObj("bunny.obj", nVerts, &vertices, &normals, &texcoords, nIndices, &indices);
}
bool testCollision(tree t) {
	float z = (t.pos.getz()+offset - 4);
	float x = (t.pos.getx() - playerX);
	float dist = sqrt(z*z+x*x);
	float totRad = t.radius + playerRad;
	if (dist < totRad) {
	//cout << "dist: " << dist << ", totRad: " << totRad << endl;
		return true;
	}
	return false;
}
void collision() {
	numHits++;
	cout << "Hit! Total = "<< numHits << endl << endl;;
}
void incOffset() {
	offset +=.05;
}
void applyOffset() {
	//camV.setz(10 - offset);
	//tarV.setz(0 - offset);
	//camera1.set(camV, tarV, upV);
	//playerPos.setz(-1 - offset);
	//ground.front = -20 - offset;
	//ground.back = 20 - offset;
}

void movePlayer() {
	if (moveLeft && playerX > -3.0)
		playerX -= .1;
	if (moveRight && playerX < 3.0)
		playerX += .1;
}

void updateTreeList() {
	while (treeList.front().pos.getz() + offset > 12) {
		treeList.pop_front();
		treeList.push_back(genTree());
	}
}

void drawGround() {
	glPushMatrix();
	glColor3f(204.0/255.0, 153.0/255.0, 102.0/255.0);
	glBegin(GL_QUADS);
		glVertex3f(ground.left, 0, ground.back);   //back left
		glVertex3f(ground.right, 0, ground.back);  //back right
		glVertex3f(ground.right, 0, ground.front); //front right
		glVertex3f(ground.left, 0, ground.front);  //front left
	glEnd();
	glPopMatrix();
}
void drawTree(tree t) {
	// glPushMatrix();
	// glTranslatef(t.pos.getx(), t.pos.gety(), t.pos.getz());
	// //call tree's draw function
	// glutSolidSphere(t.radius, 40, 40);
	// //cout << t.pos.getz()+offset << endl;
	// glPopMatrix();
	glPushMatrix();
	glTranslatef(t.pos.getx(), t.pos.gety(), t.pos.getz());
	t.treeObj->drawTreeCylinders();
	glPopMatrix();
}
void drawTrees() {
	glPushMatrix();
	glColor3f(53/255.0, 125/255.0, 27/255.0);
	glTranslatef(0, 0, offset);
	for (std::list<tree>::iterator iter = treeList.begin(), end = treeList.end(); iter != end; ++iter) {
		if (!((*iter).beenHit)) {
			if (testCollision(*iter)) {
				collision();
				(*iter).beenHit = true;
			}
		}
		drawTree(*iter);
	}
	glPopMatrix();
}

void drawPlayer() {
	// glPushMatrix();
	// glTranslatef(playerX, 1, 4);
	// glColor3f(.7, .1, .1);
	// glutSolidSphere(playerRad, 40, 40);
	// glPopMatrix();
	
	glPushMatrix();
	glTranslatef(playerX, 1, 4);
	glColor3f(.7, .1, .1);
	glRotatef(-90,0,1,0);
	glBegin(GL_TRIANGLES);
	for (int i=0; i<nIndices*3; i+=3) {
		if (i%9==0) glColor3f((i+1)/((double)nIndices*3), (i+1)/((double)nIndices*3), (nIndices*3 - i+1)/((double)nIndices*3));
		glVertex3f(vertices[i], vertices[i+1], vertices[i+2]);
		//cout<<i<<" drew a triangle: "<< vertices[i]<< " " << vertices[i+1]<< " " << vertices[i+2] << endl;
	}
	glEnd();
	glPopMatrix();
	
}

void Window::idleCallback() {
	//incOffset();
	////applyOffset();
	// movePlayer();
	// updateTreeList();
	//glutPostRedisplay();
}
void Window::reshapeCallback(int w, int h)
{
  width = w;
  height = h;
  radius = min(height, width)/2;
  glViewport(0, 0, w, h);  // set new viewport size
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  //glFrustum(-10.0, 10.0, -10.0, 10.0, 8, 1000.0); // set perspective projection viewing frustum
  glFrustum(-frustumSide, frustumSide, -1.5*frustumSide, 1.5*frustumSide, frustumNear, frustumFar); // set perspective projection viewing frustum
  
  /* moved camera position to modelview matrix */
  //glTranslatef(0, 0, -20);


}

void mouseClick(int button, int state, int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::width/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		mouseTargetX = x;
		mouseTargetY = y;
		mouseTargetZ = sqrt((float)(Window::radius*Window::radius-(mouseTargetX*mouseTargetX + mouseTargetY*mouseTargetY)));
	//Vector3 vector = Vector3(mouseTargetX, mouseTargetY, mouseTargetZ);
	//vector = hw2.matrix2.mult3(vector);
	//mouseTargetX = vector[0];
	//mouseTargetX = vector[1];
	//mouseTargetX = vector[2];
	}
}

void mouseMotion(int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::height/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		int z = sqrt((float)(Window::radius*Window::radius-(x*x + y*y)));
		
	//Vector3 vector = Vector3(x, y, z);
	//cout <<"1: "<< x<< " " << y << " " << z << endl;
	//vector = hw2.matrix2.mult3(vector);
	//x = vector[0];
	//y = vector[1];
	//z = vector[2];
	//cout << "2: "<<x<< " " << y << " " << z << endl;
		if (x!=0 && y!=0 && z!=0) {
			Vector3 vector1 = Vector3(mouseTargetX, mouseTargetY, mouseTargetZ);
			Vector3 vector2 = Vector3(x,y,z);
			Vector3 vector3 = vector1.cross(vector2);
			if (vector1.length() != 0 && vector2.length()!=0 && vector3.length()!=0){
				double angle = BasicMath::degree(asin(vector3.length()/(vector1.length()*vector2.length())));
				Matrix4 rotationMatrix, transMatrix, invTransMatrix;
				rotationMatrix.rotateArbitrary(vector3, angle);
				transMatrix.translate(0, 0, 0/*camera1.getd().getx(), camera1.getd().gety(), camera1.getd().getz()*/);
				invTransMatrix.translate(0, 0, 0/*-camera1.getd().getx(), -camera1.getd().gety(), -camera1.getd().getz()*/);
				//hw4.matrix = hw4.matrix.mult(transMatrix.mult(rotationMatrix.mult(invTransMatrix)));
				modelMatrix = rotationMatrix.mult(transMatrix.mult(modelMatrix.mult(invTransMatrix)));
				//hw2.matrix = invTransMatrix.mult(rotationMatrix.mult(transMatrix.mult(hw2.matrix)));
				//hw2.matrix2 = rotationMatrix.mult(hw2.matrix2);

				mouseTargetX = x;
				mouseTargetY = y;
				mouseTargetZ = z;
				
				//pTransRobot->set(camera1.getInverse().mult(modelMatrix));

				glutPostRedisplay();
			}
		}
	}
}

void keyboard(unsigned char key, int, int)
{
	if (key == 27) 
		exit(0);
	if (key == 'a') 
		moveLeft = true;
	if (key == 'd') 
		moveRight = true;
	
	glutPostRedisplay();
}
void keyboardUp(unsigned char key, int, int)
{
	if (key == 'a') 
		moveLeft = false;
	if (key == 'd') 
		moveRight = false;
	
	glutPostRedisplay();
}


//----------------------------------------------------------------------------
// Callback method called when window readraw is necessary or
// when glutPostRedisplay() was called.
void Window::displayCallback(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // clear color and depth buffers	
  glMatrixMode(GL_MODELVIEW);
  //glLoadMatrixd(modelMatrix.mult(camera1.getInverse()).getPointer());

	//glRotatef(90,0,1,0);
		drawGround();
		drawTrees();
		drawPlayer();

	// glBegin(GL_LINES);
	// int i=0;
	// for (list< list<Vector3> >::iterator it = (firstTree.pointsList).begin(); it != (firstTree.pointsList).end(); it++){
	// 	for (list<Vector3> ::iterator it2 = it->begin(); it2 != it->end(); it2++){
	// 		glVertex3f(it2->x,it2->y,it2->z);
	// 		//printf("%d:(%f,%f,%f)\n",i,it2->x,it2->y,it2->z);
	// 	}
	// 	i++;
	// }
	// glEnd();
	
	// glPushMatrix();
	// firstTree.drawTreeCylinders();
	// glPopMatrix();
	
  glFlush();  
  glutSwapBuffers();

}
void spinfunc(int value){
	//glRotatef(1,0,1,0);
	incOffset();
	movePlayer();
	updateTreeList();
	glutPostRedisplay();
	glutTimerFunc(0,spinfunc,0);
}

int main(int argc, char *argv[])
{
  float specular[]  = {1.0, 1.0, 1.0, 1.0};
  float shininess[] = {100.0};
  float position[]  = {6.0, 10.0, 10.0, 0.0};	// lightsource position
  
  glutInit(&argc, argv);      	      	      // initialize GLUT
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);   // open an OpenGL context with double buffering, RGB colors, and depth buffering
  glutInitWindowSize(Window::width, Window::height);      // set initial window size
  glutCreateWindow("Final Project - Speeder Bikes with Procedurally Generated Trees and SSAO");    	      // open window and set window title
  if (glutGet(GLUT_WINDOW_COLORMAP_SIZE) != 0) 
  {
    cerr << "Fatal Error: bad RGBA colormap size\n" << endl;
    exit(1);
  }
  glEnable(GL_DEPTH_TEST);            	      // enable depth buffering
  glClear(GL_DEPTH_BUFFER_BIT);       	      // clear depth buffer
  glClearColor(0.0, 0.0, 0.0, 0.0);   	      // set clear color to black
  //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);  // set polygon drawing mode to fill front and back of each polygon
  //glEnable(GL_TEXTURE_2D);   // enable texture mapping
  glShadeModel(GL_SMOOTH);             	      // set shading to smooth
  glMatrixMode(GL_PROJECTION); 
  //glPushMatrix();
  
  //// Generate material properties:
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
  glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
  //
  //// Generate light source:
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  
  // Install callback functions:
  glutDisplayFunc(Window::displayCallback);
  glutReshapeFunc(Window::reshapeCallback);
  glutIdleFunc(Window::idleCallback);


  //initializations for this project
  //modelMatrix.translate(0, 2.5, 0);
  //glutMouseFunc(mouseClick);
  //glutMotionFunc(mouseMotion);
  modelMatrix.identity();
  init();
  glutKeyboardFunc(keyboard);
  glutKeyboardUpFunc(keyboardUp);
	glutTimerFunc(200,spinfunc,0);
	glMatrixMode(GL_MODELVIEW);
  glLoadMatrixd(modelMatrix.mult(camera1.getInverse()).getPointer());


  glutMainLoop();
  return 0;
}